리스트 뷰의 원리 (중요) 리스트 뷰의 원리를 이해해야 코드를 이해할 수 있습니다. 약 100개의 정보가 담긴 List가 있다고 가정했을 때 화면에서 6개를 보여주고 스크롤을 이용해 아래로 내리면서 총 100개의 정보를 볼 수 있다고 하겠습니다. 화면상에 6개만 보입니다. 나머지 94개는 아래 있다고 생각할 수 있지만 아닙니다. ListView는 화면에 보여지는 필요한 아이템만 가져올 뿐 100개 전체를 불러오지 않습니다. 즉, 화면에 6개의 칸이 존재하고 그 칸에 계속 바꿔낍니다. 아래에서 전체의 ListView가 ViewGroup 입니다. 아래 정보가 담겨있는 한칸 한칸이 Cell 입니다.
프로젝트 생성 Custom List View 라는 이름으로 프로젝트를 생성합니다. Layout은 Empty로 설정했습니다.
minSdkVersion은 16으로 설정했습니다.
ListView 만들기 사용할 ListView를 activity_main.xml에 만들었습니다.
Layout Resource File 만들기 item_list.xml 만들겠습니다. 제목, 글쓴이, Gravity는 두 가지가 있습니다. 1) Layout gravity : 하위 View(Widget) 들을 정렬한다. 2) gravity : 내용물들을 정렬한다.
VO(Value Object) 만들기 MainActivity가 있는 패키지 안에 ArticleVO 라는 이름의 java class를 만듭니다. 제목, 글쓴이, 조회수를 담을 클래스입니다. 변수들을 만들고 생성자와 Getter, Setter를 만듭니다. 생성자와 Getter, Setter는 Alt + Insert 단축키를 이용하여 만들 수 있습니다. package com.ktds.cocomo.customlistview;
public class ArticleVO {
private String subject;
private String author;
private int hitCoutn;
public ArticleVO(String subject, String author, int hitCoutn) {
this.subject = subject;
this.author = author;
this.hitCoutn = hitCoutn;
}
public String getSubject() {
return subject;
}
public String getAuthor() {
return author;
}
public int getHitCoutn() {
return hitCoutn;
}
public void setSubject(String subject) {
this.subject = subject;
}
public void setAuthor(String author) {
this.author = author;
}
public void setHitCoutn(int hitCoutn) {
this.hitCoutn = hitCoutn;
}
}
간단한 방법 간단한 방법이지만 가장 안좋은 방법이다. 매번 convertView를 만든다. 즉, 총 10만개의 데이터가 있을 때 맨 밑으로 갔다가 다시 맨위로 올라오면 총 20만개의 convertView를 만들게된다. 메모리 낭비가 심하여 갑자기 램이 죽을 수도 있다. 객체(convertView) 생성을 최소화 시켜줄 필요가 있다. package com.ktds.cocomo.customlistview;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private ListView lvArticleList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lvArticleList = (ListView) findViewById(R.id.lvArticleList);
List articleList = new ArrayList();
for (int i = 0; i < 300; i++) {
articleList.add(new ArticleVO("제목입니다..." + i, "글쓴이입니다.", new Random().nextInt(9999)));
}
lvArticleList.setAdapter(new ArticleListViewAdapter(articleList, this));
}
private class ArticleListViewAdapter extends BaseAdapter {
private List articleList;
private Context context;
public ArticleListViewAdapter(List articleList, Context context) {
this.articleList = articleList;
this.context = context;
}
@Override
public int getCount() {
return articleList.size();
}
@Override
public Object getItem(int position) {
return articleList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if( convertView == null ) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false);
}
TextView tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
TextView tvAuthor = (TextView) convertView.findViewById(R.id.tvAuthor);
TextView tvHitCount = (TextView) convertView.findViewById(R.id.tvHitCount);
ArticleVO article = (ArticleVO) getItem(position);
tvSubject.setText(article.getSubject());
tvAuthor.setText(article.getAuthor());
tvHitCount.setText(article.getHitCoutn() + "");
return convertView;
}
}
}
가장 효율적인 방법 !! 홀더패턴이라 불린다. View 한 칸에 사용되는 것들을 홀더에 넣어놓고 convertView(한 칸)에 홀더를 setTag 시킨다. 사용할 때는 매번 생성하지 않고 convertView에 태그되어있는 홀더를 꺼내서 사용한다. 이렇게 작성하면 메모리를 거의 사용하지 않는 방법이 된다. package com.ktds.cocomo.customlistview;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.ktds.cocomo.customlistview.facebook.Facebook;
import com.restfb.types.Post;
import java.util.List;
public class MainActivity extends ActionBarActivity {
private ListView lvArticleList;
private Facebook facebook;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler();
lvArticleList = (ListView) findViewById(R.id.lvArticleList);
facebook = new Facebook(this);
facebook.auth(new Facebook.After() {
@Override
public void doAfter(Context context) {
setTimeline();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.list_menu, menu);
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
MenuItem searchButton = menu.findItem(R.id.searchButton);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchButton);
searchView.setQueryHint("검색어를 입력하세요");
searchView.setSearchableInfo( searchManager.getSearchableInfo( getComponentName() ) );
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
Intent intent = new Intent(MainActivity.this, SearchActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.putExtra("query", s);
startActivity(intent);
return false;
}
@Override
public boolean onQueryTextChange(String s) {
return false;
}
});
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.newPost) {
Intent intent = new Intent(this, WritePostActivity.class);
startActivityForResult(intent, 1000);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1000 && resultCode == RESULT_OK) {
setTimeline();
}
}
public void setTimeline() {
if (facebook.isLogin()) {
facebook.getTimeLine(new Facebook.TimelineSerializable() {
@Override
public void serialize(final List posts) {
handler.post(new Runnable() {
@Override
public void run() {
lvArticleList.setAdapter(new ArticleListViewAdapter(MainActivity.this, posts));
}
});
}
});
}
}
private class ArticleListViewAdapter extends BaseAdapter {
private List articleList;
private Post article;
private Context context;
public ArticleListViewAdapter(Context context, List articleList) {
this.articleList = articleList;
this.context = context;
}
@Override
public int getItemViewType(int position) {
article = (Post) getItem(position);
if (article.getMessage() != null && article.getMessage().length() > 0) {
return 0;
} else if (article.getStory() != null && article.getStory().length() > 0) {
return 1;
} else if (article.getStory() != null && article.getStory().length() > 0) {
return 2;
} else {
return -1;
}
}
public int getLayoutType(int index) {
if (index == 0) {
return R.layout.list_item_message;
} else if (index == 1) {
return R.layout.list_item_story;
} else if (index == 2) {
return R.layout.list_item_link;
} else {
return -1;
}
}
@Override
public int getViewTypeCount() {
return 3;
}
@Override
public int getCount() {
return articleList.size();
}
@Override
public Object getItem(int position) {
return articleList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ItemHolder holder = null;
int layoutType = getItemViewType(position);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(getLayoutType(layoutType), parent, false);
holder = new ItemHolder();
if (layoutType == 0) {
holder.tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
holder.tvAuthor = (TextView) convertView.findViewById(R.id.tvAuthor);
holder.tvHitCount = (TextView) convertView.findViewById(R.id.tvHitCount);
convertView.setOnClickListener(clickDetail(article.getId()));
} else if (layoutType == 1) {
holder.tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
} else if (layoutType == 2) {
holder.tvSubject = (TextView) convertView.findViewById(R.id.tvSubject);
holder.tvSubject.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(article.getLink()));
startActivity(intent);
}
});
}
convertView.setTag(holder);
}
else {
holder = (ItemHolder) convertView.getTag();
}
article = (Post) getItem(position);
if (layoutType == 0) {
holder.tvSubject.setText(article.getMessage());
holder.tvAuthor.setText(article.getFrom().getName());
if (article.getLikes() == null) {
holder.tvHitCount.setText("0");
} else {
holder.tvHitCount.setText(article.getLikes().getData().size() + "");
}
convertView.setOnClickListener(clickDetail(article.getId()));
} else if (layoutType == 1) {
holder.tvSubject.setText(article.getStory());
} else if (layoutType == 2) {
holder.tvSubject.setText(article.getLink());
}
return convertView;
}
}
private View.OnClickListener clickDetail (final String postId) {
return new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(view.getContext(), DetailActivity.class);
intent.putExtra("postId", postId);
startActivity(intent);
}
};
}
private class ItemHolder {
public TextView tvSubject;
public TextView tvAuthor;
public TextView tvHitCount;
}
}
Serializable 리스트 뷰의 여러 정보중 하나를 클릭했을 때 해당 정보에 대한 상세정보를 보여줄 디테일 화면을 만들겠습니다. 우선 알아두어야 할 것이 Serializable입니다. 참조를 시키고싶은 클래스에 implements Serializable 해주면 메모리가 동기화된다.
두 개의 서버에서 똑같은 클래스의 똑같은 주소의 참조를 시킬 때 Serializable을 사용한다. 예를 들어 두 개의 Activity가 완전히 다른 메모리를 사용하고 있을 때 Activity 내에서 일정 부분을 동기화(같은 메모리) 시키고 싶을 때도 Serializable을 사용한다. 배터리와 메모리 사용에 효율적이다. 아직 Serializable에 대한 개념이 어려워서 다시 정리 후 블로깅 하겠습니다!
이제 Serializable을 이용해서, intent putSerializableExtra를 통해 ArticleVO(객체)를 담아서 다른 Activity로 보내보겠습니다.
객체 intent로 보내기 우선, 상세 정보를 보여줄 DetailActivity를 만듭니다.
activity_detail.xml에서 상세 정보를 보여줄 화면(Layout)을 구성합니다. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.ktds.cocomo.customlistview.DetailActivity">
<TextView android:id="@+id/tvSubject" android:text="Subject" android:textSize="15dp" android:textColor="#000000" android:paddingLeft="5dp" android:paddingTop="5dp" android:paddingBottom="3dp" android:layout_width="match_parent" android:layout_height="wrap_content" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView android:id="@+id/tvAuthor" android:text="글쓴이" android:paddingLeft="5dp" android:layout_weight="9" android:layout_width="wrap_content" android:layout_height="match_parent" />
<TextView android:id="@+id/tvHitCount" android:text="999" android:layout_weight="1" android:gravity="right" android:paddingRight="5dp" android:layout_width="wrap_content" android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>
ArticleVO에 implements Serializable 해줍니다. 동기화(메모리 공유)시켜서 해당 객체를 intent를 통해 보내서 DetailActivity에서 객체를 받아 사용할 수 있도록 준비합니다. public class ArticleVO implements Serializable
리스트뷰에서 한 칸을 클릭했을 때, 그 칸 안에 담긴 정보를 가지고 있는 ArticleVO 객체를 putExtra를 통해 DetailActivity로 보냅니다. lvArticleList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
ArticleVO article = articleList.get(position);
Intent intent = new Intent(view.getContext(), DetailActivity.class);
intent.putExtra("article", article);
startActivity(intent);
}
});
객체 받기 MainActivity에서 보낸 ArticleVO 객체를 DetailActivity에서 받아보겠습니다. Serialize(동기화)된 클래스의 객체를 받을 땐 getSerializableExtra 를 이용합니다. public class DetailActivity extends AppCompatActivity {
private TextView tvSubject;
private TextView tvAuthor;
private TextView tvHitCount;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
tvSubject = (TextView) findViewById(R.id.tvSubject);
tvAuthor = (TextView) findViewById(R.id.tvAuthor);
tvHitCount = (TextView) findViewById(R.id.tvHitCount);
Intent intent = getIntent();
ArticleVO article = (ArticleVO) intent.getSerializableExtra("article");
tvSubject.setText(article.getSubject());
tvAuthor.setText(article.getAuthor());
tvHitCount.setText(article.getHitCoutn() + "");
}
}
디테일 화면 리스트뷰에서 4번 글을 눌러보겠습니다.
DetailActivity에서 4번 글 객체를 받아 보여줍니다.
이상으로 리스트뷰 블로깅 마치겠습니다! 감사합니다!
출처: http://cocomo.tistory.com/397?category=687308 [Cocomo Coding] |